LÀr dig att effektivt testa dina FastAPI-applikationer med TestClient. Vi tÀcker bÀsta praxis, avancerade tekniker och verkliga exempel för robusta och pÄlitliga API:er.
BemÀstra FastAPI-testning: En omfattande guide till TestClient
FastAPI har vuxit fram som ett ledande ramverk för att bygga högpresterande API:er med Python. Dess snabbhet, anvÀndarvÀnlighet och automatiska datavalidering gör det till en favorit bland utvecklare vÀrlden över. Ett vÀlbyggt API Àr dock bara sÄ bra som dess tester. Grundlig testning sÀkerstÀller att ditt API fungerar som förvÀntat, förblir stabilt under press och kan driftsÀttas i produktion med förtroende. Denna omfattande guide fokuserar pÄ att anvÀnda FastAPI:s TestClient för att effektivt testa dina API-slutpunkter.
Varför Àr testning viktigt för FastAPI-applikationer?
Testning Àr ett avgörande steg i mjukvaruutvecklingens livscykel. Det hjÀlper dig att:
- Identifiera buggar tidigt: FÄnga fel innan de nÄr produktion, vilket sparar tid och resurser.
- SÀkerstÀlla kodkvalitet: FrÀmja vÀlstrukturerad och underhÄllbar kod.
- Förhindra regressioner: Garantera att nya Àndringar inte förstör befintlig funktionalitet.
- FörbÀttra API-tillförlitlighet: Bygg förtroende för API:ets stabilitet och prestanda.
- UnderlÀtta samarbete: TillhandahÄll tydlig dokumentation om förvÀntat beteende för andra utvecklare.
Introduktion till FastAPI:s TestClient
FastAPI tillhandahÄller en inbyggd TestClient som förenklar processen att testa dina API-slutpunkter. TestClient fungerar som en lÀttviktsklient som kan skicka anrop till ditt API utan att starta en fullfjÀdrad server. Detta gör testningen betydligt snabbare och smidigare.
Huvudfunktioner i TestClient:
- Simulerar HTTP-anrop: LÄter dig skicka GET-, POST-, PUT-, DELETE- och andra HTTP-anrop till ditt API.
- Hanterar dataserrialisering: Serialiserar automatiskt anropsdata (t.ex. JSON-nyttolaster) och deserialiserar svarsdata.
- TillhandahÄller assertionsmetoder: Erbjuder praktiska metoder för att verifiera statuskod, headers och innehÄll i svaren.
- Stöder asynkron testning: Fungerar sömlöst med FastAPI:s asynkrona natur.
- Integreras med testramverk: Integreras enkelt med populÀra Python-testramverk som pytest och unittest.
SÀtta upp din testmiljö
Innan du börjar testa mÄste du sÀtta upp din testmiljö. Detta innebÀr vanligtvis att installera nödvÀndiga beroenden och konfigurera ditt testramverk.
Installation
Se först till att du har FastAPI och pytest installerade. Du kan installera dem med pip:
pip install fastapi pytest httpx
httpx Ă€r en HTTP-klient som FastAPI anvĂ€nder under huven. Ăven om TestClient Ă€r en del av FastAPI, sĂ€kerstĂ€ller installationen av httpx smidig testning. Vissa guider nĂ€mner Ă€ven requests, men httpx Ă€r mer anpassat till den asynkrona naturen hos FastAPI.
Exempel pÄ FastAPI-applikation
LÄt oss skapa en enkel FastAPI-applikation som vi kan anvÀnda för testning:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
@app.get("/")
async def read_root():
return {"message": "Hello World"}
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = None):
return {"item_id": item_id, "q": q}
@app.post("/items/")
async def create_item(item: Item):
return item
Spara denna kod som main.py. Denna applikation definierar tre slutpunkter:
/: En enkel GET-slutpunkt som returnerar ett "Hello World"-meddelande./items/{item_id}: En GET-slutpunkt som returnerar ett objekt baserat pÄ dess ID./items/: En POST-slutpunkt som skapar ett nytt objekt.
Skriva ditt första test
Nu nÀr du har en FastAPI-applikation kan du börja skriva tester med TestClient. Skapa en ny fil med namnet test_main.py i samma katalog som main.py.
from fastapi.testclient import TestClient
from .main import app
client = TestClient(app)
def test_read_root():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Hello World"}
I detta test:
- Vi importerar
TestClientoch FastAPI-instansenapp. - Vi skapar en instans av
TestClientoch skickar inapp. - Vi definierar en testfunktion
test_read_root. - Inuti testfunktionen anvÀnder vi
client.get("/")för att skicka ett GET-anrop till rot-slutpunkten. - Vi försÀkrar oss om att svarets statuskod Àr 200 (OK).
- Vi försÀkrar oss om att svarets JSON Àr lika med
{"message": "Hello World"}.
Köra dina tester med pytest
För att köra dina tester, öppna en terminal i katalogen som innehÄller din test_main.py-fil och kör följande kommando:
pytest
pytest kommer automatiskt att upptÀcka och köra alla tester i ditt projekt. Du bör se en utdata som liknar denna:
============================= test session starts ==============================
platform darwin -- Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
rootdir: /path/to/your/project
collected 1 item
test_main.py .
============================== 1 passed in 0.01s ===============================
Testa olika HTTP-metoder
TestClient stöder alla standard-HTTP-metoder, inklusive GET, POST, PUT, DELETE och PATCH. LÄt oss se hur man testar var och en av dessa metoder.
Testa GET-anrop
Vi sÄg redan ett exempel pÄ att testa ett GET-anrop i föregÄende avsnitt. HÀr Àr ett annat exempel som testar /items/{item_id}-slutpunkten:
def test_read_item():
response = client.get("/items/1?q=test")
assert response.status_code == 200
assert response.json() == {"item_id": 1, "q": "test"}
Detta test skickar ett GET-anrop till /items/1 med en query-parameter q=test. Det försÀkrar sig sedan om att svarets statuskod Àr 200 och att svarets JSON innehÄller den förvÀntade datan.
Testa POST-anrop
För att testa ett POST-anrop mÄste du skicka data i anropets body. TestClient serialiserar automatiskt datan till JSON.
def test_create_item():
item_data = {"name": "Example Item", "description": "A test item", "price": 9.99, "tax": 1.00}
response = client.post("/items/", json=item_data)
assert response.status_code == 200
assert response.json() == item_data
I detta test:
- Vi skapar en dictionary
item_datasom innehÄller data för det nya objektet. - Vi anvÀnder
client.post("/items/", json=item_data)för att skicka ett POST-anrop till/items/-slutpunkten, och skickar meditem_datasom JSON-nyttolast. - Vi försÀkrar oss om att svarets statuskod Àr 200 och att svarets JSON matchar
item_data.
Testa PUT-, DELETE- och PATCH-anrop
Att testa PUT-, DELETE- och PATCH-anrop liknar att testa POST-anrop. Du anvÀnder helt enkelt motsvarande metoder pÄ TestClient:
def test_update_item():
item_data = {"name": "Updated Item", "description": "An updated test item", "price": 19.99, "tax": 2.00}
response = client.put("/items/1", json=item_data)
assert response.status_code == 200
# LÀgg till assertions för det förvÀntade svaret
def test_delete_item():
response = client.delete("/items/1")
assert response.status_code == 200
# LÀgg till assertions för det förvÀntade svaret
def test_patch_item():
item_data = {"price": 29.99}
response = client.patch("/items/1", json=item_data)
assert response.status_code == 200
# LÀgg till assertions för det förvÀntade svaret
Kom ihÄg att lÀgga till assertions för att verifiera att svaren Àr som förvÀntat.
Avancerade testtekniker
TestClient erbjuder flera avancerade funktioner som kan hjÀlpa dig att skriva mer omfattande och effektiva tester.
Testa med beroenden (Dependencies)
FastAPI:s system för dependency injection gör det enkelt att injicera beroenden i dina API-slutpunkter. Vid testning kanske du vill ÄsidosÀtta dessa beroenden för att tillhandahÄlla mock- eller testspecifika implementationer.
Anta till exempel att din applikation Àr beroende av en databasanslutning. Du kan ÄsidosÀtta databasberoendet i dina tester för att anvÀnda en in-memory-databas:
from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import sessionmaker, declarative_base, Session
# Databaskonfiguration
DATABASE_URL = "sqlite:///./test.db" # In-memory-databas för testning
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# Definiera anvÀndarmodell
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True, index=True)
password = Column(String)
Base.metadata.create_all(bind=engine)
# FastAPI-app
app = FastAPI()
# Beroende för att hÀmta databassessionen
def get_db():
db = TestingSessionLocal()
try:
yield db
finally:
db.close()
# Slutpunkt för att skapa en anvÀndare
@app.post("/users/")
async def create_user(username: str, password: str, db: Session = Depends(get_db)):
db_user = User(username=username, password=password)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
from fastapi.testclient import TestClient
from .main import app, get_db, Base, engine, TestingSessionLocal
client = TestClient(app)
# Ă
sidosÀtt databasberoendet för testning
def override_get_db():
try:
db = TestingSessionLocal()
yield db
finally:
db.close()
app.dependency_overrides[get_db] = override_get_db
def test_create_user():
# Först, se till att tabellerna Àr skapade, vilket kanske inte sker som standard
Base.metadata.create_all(bind=engine) # viktigt: skapa tabellerna i testdatabasen
response = client.post("/users/", params={"username": "testuser", "password": "password123"})
assert response.status_code == 200
assert response.json()["username"] == "testuser"
# Rensa ÄsidosÀttningen efter testet om det behövs
app.dependency_overrides = {}
Detta exempel ÄsidosÀtter get_db-beroendet med en testspecifik funktion som returnerar en session till en in-memory SQLite-databas. Viktigt: Metadataskapandet mÄste anropas explicit för att testdatabasen ska fungera korrekt. Att misslyckas med att skapa tabellen kommer att leda till fel relaterade till saknade tabeller.
Testa asynkron kod
FastAPI Àr byggt för att vara asynkront, sÄ du kommer ofta att behöva testa asynkron kod. TestClient stöder asynkron testning sömlöst.
För att testa en asynkron slutpunkt, definiera helt enkelt din testfunktion som async:
import asyncio
from fastapi import FastAPI
app = FastAPI()
@app.get("/async")
async def async_endpoint():
await asyncio.sleep(0.1) # Simulera nÄgon asynkron operation
return {"message": "Async Hello"}
import pytest
from fastapi.testclient import TestClient
from .main import app
client = TestClient(app)
@pytest.mark.asyncio # KrÀvs för att vara kompatibel med pytest-asyncio
async def test_async_endpoint():
response = client.get("/async")
assert response.status_code == 200
assert response.json() == {"message": "Async Hello"}
Obs: Du mÄste installera pytest-asyncio för att anvÀnda @pytest.mark.asyncio: pip install pytest-asyncio. Du mÄste ocksÄ se till att asyncio.get_event_loop() Àr konfigurerad om du anvÀnder Àldre pytest-versioner. Om du anvÀnder pytest version 8 eller nyare kanske detta inte krÀvs.
Testa filuppladdningar
FastAPI gör det enkelt att hantera filuppladdningar. För att testa filuppladdningar kan du anvÀnda files-parametern i TestClient:s anropsmetoder.
from fastapi import FastAPI, File, UploadFile
from typing import List
app = FastAPI()
@app.post("/files/")
async def create_files(files: List[bytes] = File()):
return {"file_sizes": [len(file) for file in files]}
@app.post("/uploadfiles/")
async def create_upload_files(files: List[UploadFile]):
return {"filenames": [file.filename for file in files]}
from fastapi.testclient import TestClient
from .main import app
import io
client = TestClient(app)
def test_create_files():
file_content = b"Test file content"
files = [('files', ('test.txt', io.BytesIO(file_content), 'text/plain'))]
response = client.post("/files/", files=files)
assert response.status_code == 200
assert response.json() == {"file_sizes": [len(file_content)]}
def test_create_upload_files():
file_content = b"Test upload file content"
files = [('files', ('test_upload.txt', io.BytesIO(file_content), 'text/plain'))]
response = client.post("/uploadfiles/", files=files)
assert response.status_code == 200
assert response.json() == {"filenames": ["test_upload.txt"]}
I detta test skapar vi en dummyfil med io.BytesIO och skickar den till files-parametern. files-parametern accepterar en lista av tupler, dÀr varje tupel innehÄller fÀltnamnet, filnamnet och filinnehÄllet. InnehÄllstypen Àr viktig för korrekt hantering av servern.
Testa felhantering
Det Àr viktigt att testa hur ditt API hanterar fel. Du kan anvÀnda TestClient för att skicka ogiltiga anrop och verifiera att API:et returnerar korrekta felsvar.
from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: int):
if item_id > 100:
raise HTTPException(status_code=400, detail="Item ID too large")
return {"item_id": item_id}
from fastapi.testclient import TestClient
from .main import app
client = TestClient(app)
def test_read_item_error():
response = client.get("/items/101")
assert response.status_code == 400
assert response.json() == {"detail": "Item ID too large"}
Detta test skickar ett GET-anrop till /items/101, vilket genererar en HTTPException med statuskod 400. Testet försÀkrar sig om att svarets statuskod Àr 400 och att svarets JSON innehÄller det förvÀntade felmeddelandet.
Testa sÀkerhetsfunktioner
Om ditt API anvÀnder autentisering eller auktorisering mÄste du ocksÄ testa dessa sÀkerhetsfunktioner. TestClient lÄter dig stÀlla in headers och cookies för att simulera autentiserade anrop.
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
app = FastAPI()
# SĂ€kerhet
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
# Simulera autentisering
if form_data.username != "testuser" or form_data.password != "password123":
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password")
return {"access_token": "fake_token", "token_type": "bearer"}
@app.get("/protected")
async def protected_route(token: str = Depends(oauth2_scheme)):
return {"message": "Protected data"}
from fastapi.testclient import TestClient
from .main import app
client = TestClient(app)
def test_login():
response = client.post("/token", data={"username": "testuser", "password": "password123"})
assert response.status_code == 200
assert "access_token" in response.json()
def test_protected_route():
# Först, hÀmta en token
token_response = client.post("/token", data={"username": "testuser", "password": "password123"})
token = token_response.json()["access_token"]
# AnvÀnd sedan tokenen för att komma Ät den skyddade routen
response = client.get("/protected", headers={"Authorization": f"Bearer {token}"}) # korrigerat format.
assert response.status_code == 200
assert response.json() == {"message": "Protected data"}
I detta exempel testar vi inloggningsslutpunkten och anvÀnder sedan den erhÄllna tokenen för att komma Ät en skyddad route. headers-parametern i TestClient:s anropsmetoder lÄter dig stÀlla in anpassade headers, inklusive Authorization-headern för bearer-tokens.
BÀsta praxis för FastAPI-testning
HÀr Àr nÄgra bÀsta praxis att följa nÀr du testar dina FastAPI-applikationer:
- Skriv omfattande tester: Sikta pÄ hög testtÀckning för att sÀkerstÀlla att alla delar av ditt API Àr grundligt testade.
- AnvÀnd beskrivande testnamn: Se till att dina testnamn tydligt indikerar vad testet verifierar.
- Följ Arrange-Act-Assert-mönstret: Organisera dina tester i tre distinkta faser: Arrange (förbered testdata), Act (utför handlingen som testas) och Assert (verifiera resultaten).
- AnvÀnd mock-objekt: Mocka externa beroenden för att isolera dina tester och undvika att förlita dig pÄ externa system.
- Testa grÀnsfall: Testa ditt API med ogiltig eller ovÀntad indata för att sÀkerstÀlla att det hanterar fel pÄ ett elegant sÀtt.
- Kör tester ofta: Integrera testning i ditt utvecklingsflöde för att fÄnga buggar tidigt och ofta.
- Integrera med CI/CD: Automatisera dina tester i din CI/CD-pipeline för att sÀkerstÀlla att alla kodÀndringar testas grundligt innan de driftsÀtts i produktion. Verktyg som Jenkins, GitLab CI, GitHub Actions eller CircleCI kan anvÀndas för att uppnÄ detta.
Exempel: Testning av internationalisering (i18n)
NÀr man utvecklar API:er för en global publik Àr internationalisering (i18n) avgörande. Testning av i18n innebÀr att verifiera att ditt API stöder flera sprÄk och regioner korrekt. HÀr Àr ett exempel pÄ hur du kan testa i18n i en FastAPI-applikation:
from fastapi import FastAPI, Header
from typing import Optional
app = FastAPI()
messages = {
"en": {"greeting": "Hello, world!"},
"fr": {"greeting": "Bonjour le monde !"},
"es": {"greeting": "ÂĄHola Mundo!"},
}
@app.get("/")
async def read_root(accept_language: Optional[str] = Header(None)):
lang = accept_language[:2] if accept_language else "en"
if lang not in messages:
lang = "en"
return {"message": messages[lang]["greeting"]}
from fastapi.testclient import TestClient
from .main import app
client = TestClient(app)
def test_read_root_en():
response = client.get("/", headers={"Accept-Language": "en-US"})
assert response.status_code == 200
assert response.json() == {"message": "Hello, world!"}
def test_read_root_fr():
response = client.get("/", headers={"Accept-Language": "fr-FR"})
assert response.status_code == 200
assert response.json() == {"message": "Bonjour le monde !"}
def test_read_root_es():
response = client.get("/", headers={"Accept-Language": "es-ES"})
assert response.status_code == 200
assert response.json() == {"message": "ÂĄHola Mundo!"}
def test_read_root_default():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Hello, world!"}
Detta exempel stÀller in Accept-Language-headern för att specificera önskat sprÄk. API:et returnerar hÀlsningen pÄ det specificerade sprÄket. Testningen sÀkerstÀller att API:et hanterar olika sprÄkpreferenser korrekt. Om Accept-Language-headern saknas anvÀnds standardsprÄket "en".
Slutsats
Testning Àr en vÀsentlig del av att bygga robusta och pÄlitliga FastAPI-applikationer. TestClient erbjuder ett enkelt och bekvÀmt sÀtt att testa dina API-slutpunkter. Genom att följa de bÀsta praxis som beskrivs i denna guide kan du skriva omfattande tester som sÀkerstÀller kvaliteten och stabiliteten hos dina API:er. FrÄn grundlÀggande anrop till avancerade tekniker som dependency injection och asynkron testning, ger TestClient dig kraften att skapa vÀltestad och underhÄllbar kod. Omfamna testning som en central del av ditt utvecklingsflöde, sÄ kommer du att bygga API:er som Àr bÄde kraftfulla och pÄlitliga för anvÀndare över hela vÀrlden. Kom ihÄg vikten av CI/CD-integration för att automatisera testning och sÀkerstÀlla kontinuerlig kvalitetssÀkring.